﻿#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
using System.Web.Caching;
using Ninject;
using Rail.Net.Common;
using Rail.Net.Core.Interfaces.Data;
using Rail.Net.Core.Interfaces.Services;
using Rail.Net.Core.Model.Nonpersistent;

#endregion

namespace Rail.Net.Services
{
    public class ProductService : IProductService
    {
        private Cache _cache;

        public Cache Cache
        {
            get
            {
                if (_cache != null)
                {
                    return _cache;
                }
                _cache = HttpRuntime.Cache;
                return _cache;
            }
        }

        [Inject]
        public IDalContext DalContext { get; set; }

        [Inject]
        public ITransportPlanService TransportPlanService { get; set; }

        [Inject]
        public IContengentService ContengentService { get; set; }

        [Inject]
        public IEpaService EpaService { get; set; }

        #region IProductService Members

        public ProductOfferModel GetProductOffer(SearchModel searchModel, bool forwardDirection)
        {
            ProductOfferModel result = null;

            // create products based on the found connections
            var index = 0;
            try
            {
                var productOffersCacheKey = forwardDirection
                                                ? searchModel.FromProductOffersCacheKey
                                                : searchModel.ToProductOffersCacheKey;
                result = Cache[productOffersCacheKey] as ProductOfferModel;

                // if not found in cache, calculate it
                if (result == null)
                {
                    var transportPlan = TransportPlanService.GetTransportPlan(searchModel, forwardDirection);
                    TransportPlanService.PrepareDataModels(transportPlan);
                    result = new ProductOfferModel(transportPlan);

                    var task1 = Task.Factory.StartNew(() => CreateTarifProducts(searchModel, transportPlan, result), TaskCreationOptions.LongRunning);
                    var task2 = Task.Factory.StartNew(() => CreateGlobalProducts(searchModel, transportPlan, result), TaskCreationOptions.LongRunning);
                    var task3 = Task.Factory.StartNew(() => CreateContengentProducts(searchModel, transportPlan, result), TaskCreationOptions.LongRunning);

                    Task.WaitAll(task1, task2, task3);

                    // remove the invalid products
                    result.Products.RemoveAll(p => p.PriceSegments.Any(s => s.Price == 0m));

                    // correct the indices
                    for (var i = 0; i < result.Products.Count; i++)
                    {
                        result.Products[i].Index = i;
                    }

                    // store the products in cache
                    Cache.Insert(productOffersCacheKey, result, null, Cache.NoAbsoluteExpiration,
                                 new TimeSpan(1, 0, 0, 0));
                }
            }
            catch (Exception e)
            {
                var a = 9;
            }

            return result;
        }

        #endregion

        private void CreateTarifProducts(SearchModel searchModel, TransportPlanModel transportPlan, ProductOfferModel result)
        {
            var index = 0;
            foreach (var connection in transportPlan.Connections.AsParallel()
                                                                .WithDegreeOfParallelism(Constants.MaxNumberOfThreads)
                                                                .WithExecutionMode(ParallelExecutionMode.ForceParallelism))
            {
                var firstClassProducts = new List<Product2Model>
                                                     {
                                                         new Product2Model(index++, searchModel,
                                                                           connection.Sequences,
                                                                           Constants.FirstClassNumber)
                                                             {
                                                                 ServiceLevel = Constants.GlobalComfortClassValues[Constants.ServiceLevelSeat][0],
                                                                 ServiceLevelGroup = Constants.ServiceLevelSeat
                                                             }
                                                     };
                var secondClassProducts = new List<Product2Model>
                                                      {
                                                          new Product2Model(index++, searchModel,
                                                                            connection.Sequences,
                                                                            Constants.SecondClassNumber)
                                                              {
                                                                  ServiceLevel = Constants.GlobalComfortClassValues[Constants.ServiceLevelSeat][1],
                                                                  ServiceLevelGroup = Constants.ServiceLevelSeat
                                                              }
                                                      };

                var numOfInternational = connection.InternationalSequences.Count;
                if (numOfInternational > 0)
                {
                    connection.IsValid =
                        connection.InternationalSequences.All(
                            internationalSequence =>
                            CalculateInternationalPrice(internationalSequence, firstClassProducts,
                                                        secondClassProducts));
                }


                // skip those connections that didn't match with any relation
                // otherwise calculate the domestic price
                if (!connection.IsValid)
                    continue;

                foreach (var domesticSequence in connection.DomesticSequences)
                {
                    CalculateDomesticPrice(domesticSequence, firstClassProducts, secondClassProducts);
                }
                result.Products.AddRange(firstClassProducts);
                result.Products.AddRange(secondClassProducts);
            }
        }

        private void CreateGlobalProducts(SearchModel searchModel, TransportPlanModel transportPlan, ProductOfferModel result)
        {
            foreach (var connection in transportPlan.Connections.Where(c => c.IsGlobalEligable))
            {
                var globalProducts = EpaService.GetPossibleProducts(0, searchModel, connection.GlobalSequences);
                var firstClassProducts =
                    globalProducts.Where(p => p.ComfortClass == Constants.FirstClassNumber).ToList();
                var secondClassProducts =
                    globalProducts.Where(p => p.ComfortClass == Constants.SecondClassNumber).ToList();

                // calculate global price products
                var validGlobalConnection =
                    connection.GlobalInternationalSequences.All(
                        internationalSequence =>
                        CalculateInternationalPrice(internationalSequence, firstClassProducts,
                                                        secondClassProducts));
                if (validGlobalConnection)
                {
                    // calcualte the global sequence prices
                    foreach (var globalSequence in connection.GlobalOnlySequences)
                    {
                        EpaService.GetAvailability(searchModel, globalSequence, globalProducts);
                    }

                    // calcualte the domestic sequence prices
                    foreach (var domesticSequence in connection.GlobalDomesticSequences)
                    {
                        CalculateDomesticPrice(domesticSequence, firstClassProducts, secondClassProducts);
                    }

                    result.Products.AddRange(globalProducts);
                }
            }
        }

        private void CreateContengentProducts(SearchModel searchModel, TransportPlanModel transportPlan, ProductOfferModel result)
        {

            foreach (var contengentTrainModel in transportPlan.ContengentTrains)
            {
                ContengentService.GetProducts(searchModel, contengentTrainModel, result);
            }
        }

        private void CalculateDomesticPrice(DomesticSequenceModel sequence, IEnumerable<Product2Model> product1,
                                            IEnumerable<Product2Model> product2)
        {
            var distanceKmSum = sequence.DistanceModel.DistanceKmSum;
            var distanceKmSpecific = sequence.DistanceModel.DistanceKmSpecific;
            var isDomesticOnly = sequence.Connection.IsDomesticOnly;

            var priceDomestics = DalContext.PriceDomesticRepository.GetPriceDomestics(isDomesticOnly,
                                                                                      distanceKmSum - distanceKmSpecific);

            var priceSegment1 = product1.Select(p => p.GetPriceSegmentForSequence(sequence)).ToList();
            var priceSegment2 = product2.Select(p => p.GetPriceSegmentForSequence(sequence)).ToList();

            // calculate price
            priceSegment1.ForEach(
                s =>
                s.PricesProTraveler.ForEach(
                    price =>
                    price.Price = priceDomestics.FirstOrDefault(p => p.Class == Constants.FirstClassNumber).Price));
            priceSegment2.ForEach(
                s =>
                s.PricesProTraveler.ForEach(
                    price =>
                    price.Price = priceDomestics.FirstOrDefault(p => p.Class == Constants.SecondClassNumber).Price));

            // TODO: tarif calculator

            // calculate price specific
            if (distanceKmSpecific > 0)
            {
                priceDomestics = DalContext.PriceDomesticRepository.GetPriceDomestics(isDomesticOnly, distanceKmSpecific);

                priceSegment1.ForEach(
                    s =>
                    s.PricesProTraveler.ForEach(
                        price =>
                        price.Price += priceDomestics.FirstOrDefault(p => p.Class == Constants.FirstClassNumber).Price));
                priceSegment2.ForEach(
                    s =>
                    s.PricesProTraveler.ForEach(
                        price =>
                        price.Price += priceDomestics.FirstOrDefault(p => p.Class == Constants.SecondClassNumber).Price));
            }
        }

        private bool CalculateInternationalPrice(InternationalSequenceModel sequence,
                                                 IEnumerable<Product2Model> product1,
                                                 IEnumerable<Product2Model> product2)
        {
            // if no matching relation was found, skip the product creation)
            if (sequence.Relation == null)
            {
                return false;
            }

            var priceSegment1 = product1.Select(p => p.GetPriceSegmentForSequence(sequence)).ToList();
            var priceSegment2 = product2.Select(p => p.GetPriceSegmentForSequence(sequence)).ToList();

            // for each RU calculate the corresponding price share
            foreach (var ru in sequence.Relation.RelationRus)
            {
                var priceSerials = DalContext.PriceSerialRepository.GetPriceSerials(ru);
                var priceDistances = DalContext.PriceDistanceRepository.GetPriceDistances(ru);


                var firstClassPrice = 0m;
                var secondClassPrice = 0m;

                try
                {
                    var firstClassSerial =
                        priceSerials.SingleOrDefault(s => s.Class == Constants.FirstClassNumber);
                    if (firstClassSerial == null)
                    {
                        var firstClassP = priceDistances.SingleOrDefault(s => s.Class == Constants.FirstClassNumber);
                        if (firstClassP != null)
                        {
                            firstClassPrice = firstClassP.Price;
                        }
                    }
                    else
                    {
                        firstClassPrice = firstClassSerial.Price;
                    }

                    var secondClassSerial =
                        priceSerials.SingleOrDefault(s => s.Class == Constants.SecondClassNumber);
                    if (secondClassSerial == null)
                    {
                        var secondClassP = priceDistances.SingleOrDefault(s => s.Class == Constants.SecondClassNumber);
                        if (secondClassP != null)
                        {
                            secondClassPrice = secondClassP.Price;
                        }
                    }
                    else
                    {
                        secondClassPrice = secondClassSerial.Price;
                    }

                    priceSegment1.ForEach(s => s.PricesProTraveler.ForEach(p => p.Price += firstClassPrice));
                    priceSegment2.ForEach(s => s.PricesProTraveler.ForEach(p => p.Price += secondClassPrice));
                }
                catch (Exception e)
                {
                    var a = 0;
                }
            }
            return true;
            // TODO: move to tarif calculartor
            //// for each traveler calculate the corresponding price share coefficient
            //for (var i = 0; i < searchModel.Travelers.Count(); i++)
            //{
            //    var age = Int32.Parse(searchModel.Travelers[i].Age.SelectedItem.Value);

            //    firstClassProduct.InternationalShares[seqIndex][i] = (age >= 15 ? 1 : (age < 6 ? 0 : 0.5m));
            //    secondClassProduct.InternationalShares[seqIndex][i] = (age >= 15 ? 1 : (age < 6 ? 0 : 0.5m));
            //}
        }
    }
}